package elEMAS.impl;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;

import common.GeneticHelper;
import common.GeneticOperations;

import elEMAS.inf.Agent;
import elEMAS.inf.Island;

public class EmasAlgorithm<Genotype> {
    
    protected EmasParameters params;
    protected GeneticOperations<Genotype> go;
    
    protected List<Island<Genotype>> islands;
    protected int envEnergy = 0;
    
    public static int stat_numOfAgentsCreatedByEnv = 0;
    public static int stat_numOfBornAgents = 0;
    public static int stat_numOfMigrations = 0;
    public static int stat_numOfDeaths = 0;
    public static int stat_stepNumbers = 0;
    
    protected double calc_optimalHV;
    private double calc_normalHVR;
    private double calc_normalHV;
    private SortedMap<Long,Double> calc_HvInTime = new TreeMap<Long,Double>();
    
    protected long begMili;
    protected long endMili;
    
    public EmasAlgorithm(GeneticOperations<Genotype> aGo, EmasParameters aParams) {
        
        this.params = aParams;   
        this.go = aGo;  
        islands = new ArrayList<Island<Genotype>>();    
    }
    
    public void printParameters() {
        
        int p = 0;
        System.out.println("Parametry:");
        System.out.println(++p+") plik z parametrami = "+params.configFile);
        System.out.println(++p+") plik wejściowy = "+params.inputFile);
        System.out.println(++p+") ilosc krokow = "+params.stepNum);
        System.out.println(++p+") poczatkowa ilosc wszystkich agentow = "+params.agentsNum);
        System.out.println(++p+") srednio agentow na wyspie = "+(((double)params.agentsNum)/params.islandNum));
        System.out.println(++p+") ilosc wysp = "+params.islandNum);
        
        System.out.println(++p+") poczatkowa energia agenta = "+params.startAgentEnergy);
        System.out.println(++p+") prog smierci             = "+params.dieThreshold);
        System.out.println(++p+") prog reprodukcji         = "+params.reproductionThreshold);
        System.out.println(++p+") prawdopodobienstwo migracji osonika = "+params.migrationProbability);
    }

    public void printResultStatistics() {
        
        int allEnergy = 0;
        allEnergy += envEnergy;

        List<Agent<Genotype>> allAgents = getAllAgents();
        for (Agent<Genotype> agent : allAgents) {
            allEnergy += agent.getEnergy();
            assert agent.getEnergy() >= 0 : "energy: "+agent.getEnergy();
        }

        int beginEnergy = params.agentsNum * params.startAgentEnergy;
        assert allEnergy == beginEnergy : "allEnergy "+allEnergy+" != "+beginEnergy;

        System.out.println("Czas wszystkich obliczen: " + (endMili-begMili) / 1000);
        System.out.println("Ilosc agentow w systemie = "+allAgents.size());
        System.out.println("Calkowita energia w systemie = "+allEnergy);

        double generatedPrecent = ((double)stat_numOfBornAgents)/(stat_numOfBornAgents+stat_numOfAgentsCreatedByEnv)*100;
        System.out.println("Nowi agenci:");
        System.out.println("   a) utworzoni przez srodowisko = "+stat_numOfAgentsCreatedByEnv);
        System.out.println("   b) urodzeni                   = "+stat_numOfBornAgents);
        System.out.println("   b) procent urodzonych         = "+generatedPrecent+"%");

        System.out.println("Ilosc agentow usunietych z systemu:");
        System.out.println("   a) bo umarli             = "+stat_numOfDeaths);

        System.out.println("Ilosc migracji = "+stat_numOfMigrations);
        double migrationPerStep = ((double)stat_numOfMigrations) / stat_stepNumbers;
        System.out.println("Srednia ilosc migracji na krok wynosi = "+migrationPerStep);
        System.out.println("HVR ze wszystkich ("+getAllAgents().size()+") wynosi "+getHVnormal());
    } 
    
    void createAgent(Island<Genotype> targetIsland) {
        Genotype genotype = go.createRandomGenotype();
        new EmasAgent<Genotype>(targetIsland, genotype, go, islands, params);
    }

    public double calculateHV() {
        List<Agent<Genotype>> allNonDominatedAgents = GeneticHelper.withoutDominated(getAllAgents());
        return GeneticHelper.calculateHiperValume(allNonDominatedAgents, params.ref_f0, params.ref_f1);
    }
    
    public  void run() {
        
        initEnv();
        
        begMili = System.currentTimeMillis();
        int step;
        
        it_loop:
            for(step = 0; (params.stepNum == 0) || (step < params.stepNum); step++)
            {
                steps();

                if (step % params.hvCalcInterval == 0) {

                    double HV = calculateHV();
                    long time = System.currentTimeMillis() - begMili;

                    System.out.println("time "+time+" step "+step+" HV = "+HV);
                    calc_HvInTime.put(time, HV);

                    if (time > params.timeLimit) {
                        System.out.println("Czas się skończył");
                        break it_loop;
                    }
                }
            }
        
        stat_stepNumbers = step;
        
        if (step == params.stepNum) {
            System.out.println("Wykonano wszystkie kroki");
        }
        
        endMili = System.currentTimeMillis();
        calculateParams();
        
    }
    
    public void calculateParams() {
        
        List<Agent<Genotype>> allNonDominatedAgents = GeneticHelper.withoutDominated(getAllAgents());
        this.calc_normalHV = GeneticHelper.calculateHiperValume(allNonDominatedAgents,params.ref_f0,params.ref_f1);
        this.calc_normalHVR = this.calc_normalHV / this.calc_optimalHV;
    }
    
    public double getOptimalHV() {
        return this.calc_optimalHV;
    }
    
    public double getHVRnormal() {
        return this.calc_normalHVR;
    }
    
    public double getHVnormal() {
        return this.calc_normalHV;
    }
    
    public void initEnv() {
        
        Random rand = new Random();
        
        for(int i = 0; i < params.islandNum; i++) {
            islands.add(new EmasIsland<Genotype>(0));
        }
        
        //utworzenie nowych agentow
        for(int i = 0; i < params.agentsNum; i++) {
            
            int islandNum = rand.nextInt(params.islandNum); 
            createAgent(islands.get(islandNum));
        }
        
        //przekopiowanie list z nowymi agentami
        for (Island<Genotype> island: islands) {
            island.copyList();
        }    
    }
    
    public void steps() {
        
        //kolejne kroki zycia agentow
        
        for (Island<Genotype> island: islands) { 
            for(Agent<Genotype> agent: island.getAgents()) { 
                agent.LifeStep();
            }
            envEnergy += island.receiveEnergy();
        }
        
        this.tryCreateRandomAgents();
        
        for (Island<Genotype> island: islands) {
            island.copyList();  
        }
    }
    
    public void tryCreateRandomAgents() {
        
        Random rand = new Random();
        
        //System.out.println("try create env. Energia srodowiska " + _EnvEnergy + " energia poczatkowa " + _startAgentEnergy);
        while(envEnergy >= params.startAgentEnergy) {
            
            envEnergy -= params.startAgentEnergy;
            Island<Genotype> targetIsland = islands.get(rand.nextInt(params.islandNum));
            
            createAgent(targetIsland);
            stat_numOfAgentsCreatedByEnv++;
        }
    }
    
    public List<Agent<Genotype>> getAllAgents() {
        
        List<Agent<Genotype>> allAgents = new LinkedList<Agent<Genotype>>();
        for (Island<Genotype> island : islands) {
            allAgents.addAll(island.getAgents());
        }
        return allAgents;
    }
    
    public List<Agent<Genotype>> getAllNonDominatedAgents() {
        return GeneticHelper.withoutDominated(getAllAgents());
    }
    
    public SortedMap<Long, Double> getHvInTime() {
        return calc_HvInTime;
    }
}
